import TweenEngine from './engine';
import Animation from '../animation';
import Easings from './easings';
import Node from '../node';
import { colorToRGBA, getType, log } from '@/utils';

const blacklist = { node: 1, duration: 1, easing: 1, onFinish: 1, infinite: 1, yoyo: 1 };
const colorAttrs = ['color', 'fill', 'stroke', 'shadowColor'];
const updateAttrs = ['x', 'y', 'width', 'height', 'rotation', 'scale', 'fontSize', 'lineHeight'];

/**
 * 渐变动画类
 */
export default class Tween {
  className = 'Tween'; // 类名
  constructor(config) {
    const { node, duration, easing, infinite, yoyo } = config;
    if (!node) throw new Error('要使用渐变动画的节点不存在！');
    Tween.tweens.push(this);
    this.id = Tween.tweenIdCounter++;
    // 节点
    this.node = node;
    // 持续时间
    if (duration === undefined) this.duration = 0.3;
    else if (duration <= 0) this.duration = 0.001;
    else this.duration = duration;
    // 缓动函数
    this.easing = easing ?? Easings.Linear;
    const { stage } = node;
    if (!stage) log.warn('要使用渐变动画请先将节点添加到 stage 中！');
    // 实例化渐变引擎对象
    this.tween = new TweenEngine((p) => this.tweenFunc(p), this.easing, 0, 1, this.duration * 1000, !!infinite, !!yoyo);
    // 实例化动画对象
    this.anim = new Animation(() => this.tween.onEnterFrame(), stage);
    // 添加事件监听
    this.addListeners();
    // 初始化节点映射对象
    if (!Tween.attrs[node.id]) Tween.attrs[node.id] = {};
    if (!Tween.attrs[node.id][this.id]) Tween.attrs[node.id][this.id] = {};
    if (!Tween.nodes[node.id]) Tween.nodes[node.id] = {};
    // 添加属性
    for (const key in config) if (!blacklist[key]) this.addAttr(key, config[key]);
    this.reset();
    this.onFinish = config.onFinish;
    this.onReset = config.onReset;
    this.onUpdate = config.onUpdate;
  }
  // 添加属性
  addAttr(key, end) {
    const { id, node } = this;
    let start = node[key];
    let diff;
    if (getType(end) === 'Array') {
      diff = [];
      const len = Math.max(end.length, start.length);
      if (['color', 'fill'].includes(key)) {
        for (let n = 0; n < len; n++) {
          if (n % 2 === 0) diff.push(end[n] - start[n]);
          else {
            const startRGBA = colorToRGBA(start[n]);
            const endRGBA = colorToRGBA(end[n]);
            start[n] = startRGBA;
            diff.push({
              r: endRGBA.r - startRGBA.r,
              g: endRGBA.g - startRGBA.g,
              b: endRGBA.b - startRGBA.b,
              a: endRGBA.a - startRGBA.a,
            });
          }
        }
      } else {
        for (let n = 0; n < len; n++) diff.push(end[n] - start[n]);
      }
    } else if (colorAttrs.includes(key)) {
      start = colorToRGBA(start);
      const endRGBA = colorToRGBA(end);
      diff = {
        r: endRGBA.r - start.r,
        g: endRGBA.g - start.g,
        b: endRGBA.b - start.b,
        a: endRGBA.a - start.a,
      };
    } else {
      diff = end - start;
    }
    const tweenId = Tween.nodes[node.id][key];
    if (tweenId) delete Tween.attrs[node.id][tweenId][key];
    Tween.attrs[node.id][id][key] = { start, diff, end };
    Tween.nodes[node.id][key] = id;
  }
  // 渐变函数
  tweenFunc(p) {
    const { id, node } = this;
    const attrs = Tween.attrs[node.id][id];
    let needUpdate = false;
    for (const key in attrs) {
      if (updateAttrs.includes(key)) needUpdate = true;
      const attr = attrs[key];
      const { start, diff, end } = attr;
      let newVal;
      if (getType(start) === 'Array') {
        newVal = [];
        const len = Math.max(start.length, end.length);
        if (['color', 'fill'].includes(key)) {
          for (let n = 0; n < len; n++) {
            if (n % 2 === 0) newVal.push((start[n] || 0) + diff[n] * p);
            else {
              newVal.push(
                'rgba(' +
                  Math.round(start[n].r + diff[n].r * p) +
                  ',' +
                  Math.round(start[n].g + diff[n].g * p) +
                  ',' +
                  Math.round(start[n].b + diff[n].b * p) +
                  ',' +
                  (start[n].a + diff[n].a * p) +
                  ')',
              );
            }
          }
        } else {
          for (let n = 0; n < len; n++) newVal.push((start[n] || 0) + diff[n] * p);
        }
      } else if (colorAttrs.includes(key)) {
        newVal =
          'rgba(' +
          Math.round(start.r + diff.r * p) +
          ',' +
          Math.round(start.g + diff.g * p) +
          ',' +
          Math.round(start.b + diff.b * p) +
          ',' +
          (start.a + diff.a * p) +
          ')';
      } else {
        newVal = start + diff * p;
      }
      node[key] = newVal;
    }
    if (needUpdate) {
      node.update();
      node.updateParent();
      node.stage?.transformer.update(node);
    }
  }
  // 添加事件监听
  addListeners() {
    this.tween.onPlay = () => this.anim.start();
    this.tween.onReverse = () => this.anim.start();
    this.tween.onPause = () => this.anim.stop();
    this.tween.onFinish = () => this.onFinish?.call(this);
    this.tween.onReset = () => this.onReset?.();
    this.tween.onUpdate = () => this.onUpdate?.call(this);
  }
  /**
   * Methods
   */
  // 运行
  play() {
    this.tween.play();
    return this;
  }
  // 反转
  reverse() {
    this.tween.reverse();
    return this;
  }
  // 重置
  reset() {
    this.tween.reset();
    return this;
  }
  // 跳转到某个时间点
  seek(second) {
    this.tween.seek(second * 1000);
    return this;
  }
  // 暂停
  pause() {
    this.tween.pause();
    return this;
  }
  // 结束
  finish() {
    this.tween.finish();
    return this;
  }
  // 销毁
  destroy() {
    const { id, node } = this;
    this.pause();
    const attrs = Tween.nodes[node.id];
    for (const key in attrs) delete attrs[key];
    delete Tween.attrs[node.id][id];
    const index = Tween.tweens.findIndex((i) => i.id === id);
    if (index > -1) Tween.tweens.splice(index, 1);
  }
}

Tween.attrs = {};
Tween.nodes = {};
Tween.tweens = [];
Tween.tweenIdCounter = 0;

// 给 Node 类添加配置更新的渐变方法
Node.prototype.to = function (params) {
  const onFinish = params.onFinish;
  params.node = this;
  params.onFinish = function () {
    this.destroy();
    onFinish?.();
    params.node.stage?.handleRecord();
  };
  const tween = new Tween(params);
  tween.play();
};
